﻿#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;

namespace AbstractClass.Threading.Tasks
{
    /// <summary>
    /// Represents an operation.
    /// </summary>
    /// <typeparam name="TResult">The type of the result of operation.</typeparam>
    public abstract class Job<TResult>
    {
        private readonly string _jobId;

        /// <summary>
        /// Initializes a new instance of the <see cref="Job&lt;TResult&gt;"/> class.
        /// </summary>
        protected Job()
        {
            _jobId = Guid.NewGuid().ToString();
        }

        /// <summary>
        /// Gets or sets the result of the operation.
        /// </summary>
        /// <value>The result of the operation.</value>
        public TResult Result { get; internal set; }

        /// <summary>
        /// Gets or sets the engine which executes the operation.
        /// </summary>
        /// <value>The engine which executes the operation.</value>
        public JobEngine<TResult> Engine { get; set; }

        /// <summary>
        /// Gets the unique id associated with a job.
        /// </summary>
        /// <value>The unique job id.</value>
        public string JobId
        {
            get { return _jobId; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="Job&lt;TResult&gt;"/> is processed.
        /// </summary>
        /// <value><c>true</c> if processed; otherwise, <c>false</c>.</value>
        public virtual bool Processed { get; protected internal set; }

        /// <summary>
        /// Begins an asynchronous operation.
        /// </summary>
        /// <param name="callback">The <see cref="AsyncCallback"/> delegate.</param>
        /// <param name="state">The state object for this operation.</param>
        /// <returns>The status of the asynchronous operation.</returns>
        protected internal abstract IAsyncResult BeginInvoke(AsyncCallback callback, object state);

        /// <summary>
        /// Ends an asynchronous operation.
        /// </summary>
        /// <param name="asyncResult">The status of this asynchronous operation.</param>
        /// <returns>The result of the operation.</returns>
        protected internal abstract TResult EndInvoke(IAsyncResult asyncResult);

        /// <summary>
        /// Executes the operation.
        /// </summary>
        /// <returns>The result of the operation.</returns>
        protected internal abstract TResult Invoke();

        /// <summary>
        /// Schedules <paramref name="jobB"/> to be run sequentially after the execution of <paramref name="jobA"/>.
        /// </summary>
        /// <param name="jobA">The first job to run.</param>
        /// <param name="jobB">The job to run after <paramref name="jobA"/>.</param>
        /// <returns>A <see cref="JobEngine&lt;TResult&gt;"/> instance to start and monitor the execution.</returns>
        /// <exception cref="InvalidOperationException">Job scheduling fails.</exception>
        public static JobEngine<TResult> operator +(Job<TResult> jobA, Job<TResult> jobB)
        {
            try
            {
                if (jobA.Engine == null)
                {
                    var engine = new JobEngine<TResult>();
                    engine.Enqueue(jobA);
                }
                // ReSharper disable PossibleNullReferenceException
                jobA.Engine.EnqueueSequential(jobA, jobB);
                // ReSharper restore PossibleNullReferenceException
                return jobA.Engine;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(LanguageResource.JobSequenceOperationFailed, ex);
            }
        }

        /// <summary>
        /// Schedules <paramref name="jobB"/> to be run parallely with <paramref name="jobA"/>.
        /// </summary>
        /// <param name="jobA">The first job to run.</param>
        /// <param name="jobB">The job to run parallely with <paramref name="jobA"/>.</param>
        /// <returns>A <see cref="JobEngine&lt;TResult&gt;"/> instance to start and monitor the execution.</returns>
        /// <exception cref="InvalidOperationException">Job scheduling fails.</exception>
        public static JobEngine<TResult> operator -(Job<TResult> jobA, Job<TResult> jobB)
        {
            try
            {
                if (jobA.Engine == null)
                {
                    var engine = new JobEngine<TResult>();
                    engine.Enqueue(jobA);
                }
                // ReSharper disable PossibleNullReferenceException
                jobA.Engine.EnqueueParallel(jobA, jobB);
                // ReSharper restore PossibleNullReferenceException
                return jobA.Engine;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(LanguageResource.JobParallelOperationFailed, ex);
            }
        }

        /// <summary>
        /// Schedules all jobs queued in <paramref name="engine"/> to be run after the execution of <paramref name="job"/>.
        /// </summary>
        /// <param name="job">The first job to run.</param>
        /// <param name="engine">A <see cref="JobEngine&lt;TResult&gt;"/> instance containing other scheduled jobs.</param>
        /// <returns>A <see cref="JobEngine&lt;TResult&gt;"/> instance to start and monitor the execution.</returns>
        /// <exception cref="InvalidOperationException">Job scheduling fails.</exception>
        public static JobEngine<TResult> operator +(Job<TResult> job, JobEngine<TResult> engine)
        {
            try
            {
                if (job.Engine == null)
                {
                    var engineA = new JobEngine<TResult>();
                    engineA.Enqueue(job);
                }
                foreach (var element in engine.JobQueue)
                {
                    if (job.Engine != null) job.Engine.EnqueueRange(element);
                }
                return job.Engine;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(LanguageResource.JobSequenceOperationFailed, ex);
            }
        }

        /// <summary>
        /// Schedules first jobs queued in <paramref name="engine"/> to be run parallely with <paramref name="job"/>.
        /// </summary>
        /// <param name="job">The first job to run.</param>
        /// <param name="engine">A <see cref="JobEngine&lt;TResult&gt;"/> instance containing other scheduled jobs.</param>
        /// <returns>A <see cref="JobEngine&lt;TResult&gt;"/> instance to start and monitor the execution.</returns>
        /// <exception cref="InvalidOperationException">Job scheduling fails.</exception>
        public static JobEngine<TResult> operator -(Job<TResult> job, JobEngine<TResult> engine)
        {
            try
            {
                if (job.Engine == null)
                {
                    var engineA = new JobEngine<TResult>();
                    engineA.Enqueue(job);
                }
                foreach (var element in engine.JobQueue)
                {
                    if (job.Engine != null) job.Engine.EnqueueRange(element);
                }
                return job.Engine;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(LanguageResource.JobSequenceOperationFailed, ex);
            }
        }
    }
}